<?php
/**
 * User: zsd <zsdit@qq.com>
 * Date: 2016/2/27
 * Time: 13:51
 *
 * 规则：
 *  一：
 *
 * -- 预排序遍历算法 获取分类
CREATE TABLE if NOT EXISTS mythink_sort_modified(

id int not null auto_increment,
`name` varchar(100) not null comment '分类名称',
`lef` SMALLINT  not null comment '左代码',
`rig` SMALLINT  not null comment '右代码',
`level` tinyint DEFAULT 0 comment '级别',

--   UNIQUE (`lef`,`rig`),
PRIMARY KEY (id)

)engine=myisam DEFAULT charset=utf8 comment='预排序遍历算法处理分类';
 */
namespace Home\Model;

use Think\Model;

class SortModifiedModel extends  Model{
    /*
     * 自动验证
     */
    protected $_validate = array(
        array('name','require','名称必须存在',self::MUST_VALIDATE),
        array('lef','require','左编码必须存在',self::MUST_VALIDATE),
//        array('lef','','该左编码已经存在',self::MUST_VALIDATE,'unique'),
        array('rig','require','右编码必须存在',self::MUST_VALIDATE),
//        array('rig','','该右编码已存在',self::MUST_VALIDATE,'unique'),
    );

    /*
     * 自动完成
     */
    protected $_auto    = array();


    /**
     * 插入一条 数据 --
     *      技术实现：1、采用事务回滚 机制，避免整个分类爆炸了
     *               2、尝试使用 延迟跟新机制（舍弃，这个修改比较少 压力不大）
     * @param int $pid :要操作的pid
     * @param array $info : 要插入的数据
     * @param int $left_id : 左边的元素id
     * @return boolean
     * @author zsd <zsdit@qq.com>
     */
    public function insertSort($info,$pid=0,$left_id=null){

        //计算要插入节点的前一个节点信息
        if($left_id){//指定了左边节点
            $left_info = $this->where(array('id'=>$left_id))->find();
        }else{
            $parent_info = $pid ? $this->where(array('id'=>$pid))->find() : null;
            $have_parent_map = array(
                'rig'=>array('lt',$parent_info['rig']),
                'lef'=>array('gt',$parent_info['lef']),
            );
            $left_info = $parent_info ? $this->where($have_parent_map)->order('rig desc')->find() : $this->order('rig desc')->find();
        }

        if($left_info){
            //同级别下有 左侧数据
            $ok = $this->have_same_level($info,$left_info);
        }else{
            $ok = $this->no_same_level($info,$parent_info);
        }
        return $ok;
    }

    /**
     * 处理 具有同一级别数据
     * @param array $info : 提交数据
     * @param array $left_info : 左边的数据
     * @return boolean
     * @author zsd <zsdit@qq.com>
     */
    protected function have_same_level($info,$left_info){
        $data = array(
            'name'  => $info['name'],
            'lef'   => $left_info['rig'] + 1,
            'rig'   => $left_info['rig'] + 2,
//            'level' => $left_info['level'],
        );

        //处理 变动 数据
        $need_up_lef_map = array(
            'lef'   => array('gt',$data['lef']),
        );
        $need_up_rig_map = array(
            'rig'   => array('egt',$data['lef']),
        );

        $this->startTrans();
        $this->where($need_up_lef_map)->setInc('lef',2);
        $this->where($need_up_rig_map)->setInc('rig',2);

        $can_data = $this->create($data);
        if(!$can_data){
            $this->rollback();
            $this->error = $this->getError();
            return false;
        }

        if($this->add($can_data)){
            $this->commit();
            return true;
        }else{
            $this->rollback();
            $this->error =$this->getError();
            return false;
        }
    }


    /**
     * 添加--同一级别，同一父id 没有 左侧节点
     * @param array $info : 要添加节点信息
     * @param array  $parent_info : 所属父节点信息
     * @return boolean
     * @author zsd <zsdit@qq.com>
     */
    protected function no_same_level($info,$parent_info){
        if(!$parent_info){
            $data = array(
                'name'  => $info['name'],
                'lef'   => 1,
                'rig'   => 2,
//                'level' => 1,
            );
            return $this->add($data);
        }else{
            $data = array(
                'name'  => $info['name'],
                'lef'   => $parent_info['lef'] + 1,
                'rig'   => $parent_info['rig'] + 1,
//                'level' => $parent_info['level'] + 1,
            );

            //处理 变动 数据
            $need_up_lef_map = array(
                'lef'   => array('gt',$data['lef']),
            );
            $need_up_rig_map = array(
                'rig'   => array('egt',$parent_info['rig']),
            );

            $this->startTrans();
            $this->where($need_up_lef_map)->setInc('lef',2);
            $this->where($need_up_rig_map)->setInc('rig',2);

            $can_data = $this->create($data);
            if(!$can_data){
                $this->rollback();
                $this->error = $this->getError();
                return false;
            }

            if($this->add($can_data)){
                $this->commit();
                return true;
            }else{
                $this->rollback();
                $this->error =$this->getError();
                return false;
            }
        }

    }


    /**
     * 获取 节点列表
     * @param int $need_node : 需要获取的节点id
     * @param int $no_node : 不需要获取的节点id
     * @return array : 节点列表
     * @author zsd <zsdit@qq.com>
     */
    public function getLists($need_node=0,$no_node=null){
        $map = '';

        if($need_node){
            $need_node = $this->where(array('id'=>$need_node))->find();
            if(empty($need_node)){
                $this->error = '非法节点';
                return false;
            }
            $map .= ' and node.lef between '.($need_node['lef']+1).' and '.($need_node['rig']-1);
        }

        //TODO :: 需要得到的节点下所有信息，和排除某些 节点的信息
        if($no_node){
            $no_node = $this->where(array('id'=>$no_node))->find();
            if(empty($no_node)){
                $this->error = '非法移动节点';
                return false;
            }
            $map .= ' and node.lef not between '.$no_node['lef'].' and '.$no_node['rig'];
        }

        $sql = 'select node.id,node.name,node.lef,node.rig , (count(parent.name)-1) as level
from
mythink_sort_modified as node,
mythink_sort_modified as parent
where node.lef between parent.lef and parent.rig '.$map.'
group by node.name
order by node.lef;';

        $lists = $this->query($sql);
        return $lists;

    }


    /**
     * 移动节点
     *      type : 1、 移动到某节点 之后
     *             2、 移动到某节点之前
     *             3、 移动到某节点下面（默认最后）
     *
     * @param array $post : 得到的数据
     * @return boolean
     * @author zsd <zsdit@qq.com>
     */
    public function node_move($post){
        dump($post);
        $move_node = $this->find($post['move_id']);
        $aim_node = $this->find($post['aim_id']);
        switch($post['move_type']){
            case 1:
                if(empty($aim_node)) return false;
                $ok = $this->MoveNodeAfter($move_node,$aim_node);
                break;
            case 2:
                break;
            case 3:
                break;
            default:
                $this->error = '错误操作';
                return false;
                break;
        }
        return $ok;
    }

    /**
     * 移动到点之后  | 移动节点的时候 发现 ，level 并不好操作，将其干掉 | 干不掉啊，mysql 不溜 自己查不出来 级别 | 哈哈 老子找到了获取级别的sql了，麻痹干掉
     *
     *  注意： 1、移动不同于插入，不能以左右节点作为判断依据，因为其实可变，不是可控制因素。 *
     *        2、对于有包含关系时，左右节点都跟新会出错。                                * 方案：左右节点分离原则，左边只跟新左边，右边的跟新右边。
     *                                                                              *
     * @param array $move_node : 移动点信息
     * @param array $aim_node : 目标点信息
     * @return boolean
     * @author zsd <zsdit@qq.com>
     */
    private function MoveNodeAfter($move_node,$aim_node){
        /*计算移动距离*/
        $between_number = abs($move_node['rig']-$move_node['lef'] + 1); //第一次移动距离
        $move_number    = abs($aim_node['lef'] - $move_node['rig']+1); //第二次移动距离
        if($move_node['lef'] < $aim_node['lef'] ){
            //移动点在目标点前面
            $this->startTrans();
            /*第一次移动：两点之间，包含目标点 。左移动点计算id*/
            $first_move_lef_map = array(
                'lef'   => array('between',array($move_node['rig'],$aim_node['rig'])),
            );
            $first_move_lef_id = $this->field('id')->where($first_move_lef_map)->select();
            $first_move_lef_id = array_column($first_move_lef_id,'id');
            /*第一次移动：两点之间，包含目标点 。右移动点计算id*/
            $first_move_rig_map = array(
                'rig'   => array('between',array($move_node['rig']+1,$aim_node['rig'])),
            );
            $first_move_rig_id = $this->field('id')->where($first_move_rig_map)->select();
            $first_move_rig_id = array_column($first_move_rig_id,'id');

            /*第二次 要移动的数据 ，移动节点 及其子节点,这个就可以 左右点一起变了*/
            $sencond_move_map = array(
                'lef'   => array('between',array($move_node['lef'],$move_node['rig'])),
            );
            $sencond_move_id = $this->field('id')->where($sencond_move_map)->select();
            $sencond_move_id = array_column($sencond_move_id,'id');

            //开始移动
            $first_move_lef_id = implode(',',$first_move_lef_id);
            $ok1 = $this->where(array('id'=>array('in',$first_move_lef_id)))->setDec('lef',$between_number);
            $first_move_rig_id = implode(',',$first_move_rig_id);
            $ok2 = $this->where(array('id'=>array('in',$first_move_rig_id)))->setDec('rig',$between_number);

            $sencond_move_id = implode(',',$sencond_move_id);
            $ok3 = $this->where(array('id'=>array('in',$sencond_move_id)))->setInc('rig',$move_number);
            $ok4 = $this->where(array('id'=>array('in',$sencond_move_id)))->setInc('lef',$move_number);

            $check = $this->checkSort();

            if($ok1 && $ok2 && $ok3 && $ok4 && $check){
                $this->commit();
                return true;
            }else{
                $this->rollback();
                return false;
            }
        }
        else{
            $this->error = 'ERROR:001,该功能未开放';
            return false;
        }
    }


    /**
     * 预编译排序算法 ，合理性检查即 当前排序真确信验证
     * @author zsd <zsdit@qq.com>
     */
    public function checkSort(){
        //TODO::预编译排序算法 ，合理性检查 ,在事务时的检查（这个需要测试）
        return true;
    }

}